package com.example.docsearcher.search;

import com.example.docsearcher.Config;
import org.ansj.domain.Term;
import org.ansj.splitWord.analysis.ToAnalysis;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.*;

public class DocSearch {

    // 加载暂停词表
    private static String STOP_WORD_PATH = null;

    // 根据开关来判断是否为线上环境
    static {
        if(Config.IS_ONLINE){
            STOP_WORD_PATH = "";
        } else {
            STOP_WORD_PATH = "D:\\program\\idea-ultimate\\api\\jdk-8u461-docs-all\\stop_word.txt";
        }
    }
    private HashSet<String> stopWord = new HashSet<String>();
    Index index = new Index();
    Parser parser = new Parser();
    public DocSearch() throws InterruptedException {
        parser.run();
        index.load();
        loadStopWord();
    }

    // 传入的参数就是用户输入的查询词， 返回值为查到的 Result
    public List<DocResult> search(String searchText) {

        // 1 根据用户输入的词语进行分词
        List<Term> oldTerm = ToAnalysis.parse(searchText).getTerms();
        List<Term> termList = new ArrayList<>();


        for (Term term : oldTerm) {
            // 过滤暂停词
            if(stopWord.contains(term.getName())) {
                continue;
            }
            termList.add(term);
        }

        // 2 根据分词结果查倒排索引
        // 但这里有一个问题，会出现多个分词结果都查询到相同的一个文档，这里要将文档进行去重，并且将出现多次的结果权重相加
        // 2.1 创建一个二维数组将每个分词结果查询到的倒排拉链进行存储
        List<List<Weight>> tokenResult =  new ArrayList<>();
        for( Term term : termList ){
            List<Weight> invertIndex = index.getInverted(term.getName());

            if(invertIndex == null){
                continue;
            }
            // 将每个分词结果查询到的倒排索引都放到这个二维数组中
            tokenResult.add(invertIndex);
        }

        // 3 将二维数组进行去重并且权重相加
        List<Weight> allTokenResult = mergeTokenResult(tokenResult);

        // 4 将查询结果根据 weight 进行排序
        allTokenResult.sort(new  Comparator<Weight>() {
            @Override
            public int compare(Weight o1, Weight o2) {
                // 升序就是 o1 - o2， 降序就是 o2 - o1
                return o2.getWeight() - o1.getWeight();
            }
        });

        List<DocResult> results = new ArrayList<>();
        // 5 将最终结果进行包装
        for( Weight weight : allTokenResult ){
            DocResult docResult = new DocResult();
            // 根据排序后的 docId 查正排
            DocInfo docInfo = index.getDocInfo(weight.getDocId());
            docResult.setTitle(docInfo.getTitle());
            docResult.setDocId(docInfo.getDocId());
            docResult.setUrl(docInfo.getUrl());
            docResult.setDesc(GenContent(docInfo.getContent(), termList));
            results.add(docResult);
        }
        return results;
    }

    // 用来存储一个 weight 对象在二维数组中的位置
    static class Pos{
        public int row = 0;
        public int col = 0;

        public  Pos(int row, int col){
            this.row = row;
            this.col = col;
        }
    }

    private List<Weight> mergeTokenResult(List<List<Weight>> tokenResult) {
        List<Weight> allTokenResult = new ArrayList<>();
        // 1 将二维数组中每个子元素数组根据 docId 进行升序排序
        for( List<Weight> invertIndex : tokenResult ){
            invertIndex.sort(new  Comparator<Weight>() {
                @Override
                public int compare(Weight o1, Weight o2) {
                    // 升序就是 o1 - o2， 降序就是 o2 - o1
                    return o1.getDocId() - o2.getDocId();
                }
            });
        }
        // 2 创建一个优先级队列
        PriorityQueue<Pos> queue = new PriorityQueue<>(new Comparator<Pos>() {
            @Override
            public int compare(Pos o1, Pos o2) {
                // 指定排序规则
                int id1 = tokenResult.get(o1.row).get(o1.col).getDocId();
                int id2 = tokenResult.get(o2.row).get(o2.col).getDocId();
                return id1 - id2;
            }
        });
        // 3 将数组中每行的首元素（也就是每个分词结果查到的倒排拉链中 docId 最小的）放进队列中
        for(int i = 0; i < tokenResult.size(); i++){
            queue.offer(new Pos(i, 0));
        }

        // 4 将所有结果合入最终结果中
        while(!queue.isEmpty()){
            Pos curPos = queue.poll();
            // 1 获取当前位置对应的元素
            Weight curWeight = tokenResult.get(curPos.row).get(curPos.col);
            // 2 先判断结果数组中是否有元素
            if(!allTokenResult.isEmpty()){
                // 3 取出数组的首元素比较与当前元素的 docId 是否相同
                Weight lastWeight = allTokenResult.get(allTokenResult.size()-1);
                // 4 如果相同则进行权重合并
                if(curWeight.getDocId() == lastWeight.getDocId()){
                    lastWeight.setWeight(curWeight.getWeight() + lastWeight.getWeight());
                } else {
                    // 不相同则直接插入
                    allTokenResult.add(curWeight);
                }
            } else {
                // 若当前数组中没有元素则直接插入
                allTokenResult.add(curWeight);
            }
            Pos newPos = new Pos(curPos.row, curPos.col+1);
            if(newPos.col >= tokenResult.get(newPos.row).size()){
                // 当前已经到 行末
                continue;
            }
            queue.offer(newPos);
        }
        return allTokenResult;
    }

    private String GenContent(String content, List<Term> termList) {
        int first = -1;
        for( Term term : termList ){
            String word = term.getName();
            // 到正文中去查找用户输入的查询词
            // 避免由于查询词左右两侧有特殊符号的情况,将查询词使用正则表达式查询出来，再将其替换成左右包含空格的情况
            content = content.toLowerCase().replaceAll("\\b" + word + "\\b", " " + word + " ");
            first = content.indexOf(" "+ word + " ");
            // 当在正文中查找到该词语时就可以停止循环
            if(first >= 0){
                break;
            }
        }
        // 有可能找不到这个分词结果
        if(first == -1){
            if(content.length() > 160){
                return content.substring(0, 160);
            }
            return content;
        }
        // 将这个词的前60个字符到后100个字符进行截取
        String desc = "";
        // 当前的词的位置离起始位置 < 60 个字符
        int firstPos  = first < 60 ? 0 : first - 60 ;
        // 起始位置到文章末尾不够 160 字符
        if(firstPos + 160 > content.length()){
            desc = content.substring(firstPos);
        }
        else {
            desc = content.substring(firstPos , firstPos + 160) + "...";
        }

        //此处实现标红逻辑，给 desc 中与查询词一致的地方标红， 加入i标签后，字体变为斜体，在前端通过标签属性名将<i>标签里的内容标红
        for(Term term : termList ){
            String word = term.getName();
            // 这里要进行全词匹配，避免将其他词语中包含查询词的地方标红
            desc = desc.replaceAll(" " + word + " ","<i> " + word + " </i>");
        }
        return desc;
    }

    private void loadStopWord(){
        try(BufferedReader bufferedReader = new BufferedReader(new FileReader(STOP_WORD_PATH))){
            while(true) {
                String line = bufferedReader.readLine();
                if(line == null){
                    break;
                }
                stopWord.add(line);
            }
        } catch(IOException e){
            e.printStackTrace();
        }
    }
    public static void main(String[] args) throws InterruptedException {
        DocSearch docSearch = new DocSearch();
        Scanner sc = new Scanner(System.in);

        System.out.println("->");
        String searchText = sc.nextLine();
        List<DocResult> results = docSearch.search(searchText);
        for (DocResult docResult : results) {
            System.out.println("查询结果为：" + docResult);
        }
    }

}
